package ch.ethz.fcl.metrobuzz.data.scene;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xml.sax.Attributes;

import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Edge;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.data.scene.spacetime.Agent;
import ch.ethz.fcl.metrobuzz.data.scene.spacetime.Itinerary;
import ch.ethz.fcl.metrobuzz.data.scene.spacetime.Trip;
import ch.ethz.fcl.metrobuzz.data.transit.Stop;

public final class MBScene {
	private MBData data;

	private List<Node> nodes = null;
	private Map<String, Integer> nodeIndexMap = null;

	private List<Edge> edges = null;
	private Map<String, Integer> edgeIndexMap = null;
	// start and end node id to edge map
	private Map<String, Edge> startEndEdgeMap = null;

	private Map<Long, Agent> agentIdMap;

	private int mrtTrip = 0;

	private SceneBound bound;

	private List<Node> mrtNodes;
	private List<Node> ewLine;
	private List<Node> nsLine;
	private List<Node> neLine;
	private List<Node> ccLine;
	private List<Node> cgLine;

	public MBScene(MBData data) {
		this.data = data;

		nodes = new ArrayList<Node>();
		nodeIndexMap = new HashMap<String, Integer>();

		edges = new ArrayList<Edge>();
		edgeIndexMap = new HashMap<String, Integer>();
		startEndEdgeMap = new HashMap<String, Edge>();

		agentIdMap = new HashMap<Long, Agent>();

		bound = new SceneBound();

		mrtNodes = new ArrayList<Node>();
		ewLine = new ArrayList<Node>();
		nsLine = new ArrayList<Node>();
		neLine = new ArrayList<Node>();
		ccLine = new ArrayList<Node>();
		cgLine = new ArrayList<Node>();
	}

	public MBData getData() {
		return data;
	}

	public void addNode(Attributes attributes) {
		// attributes: id, x, y, type
		String id = attributes.getValue("id");
		String x = attributes.getValue("x");
		String y = attributes.getValue("y");

		if (nodeIndexMap.get(id) == null) {
			try {
				float fx = Float.parseFloat(x);
				float fy = Float.parseFloat(y);
				nodeIndexMap.put(id, nodes.size());

				Node node = new Node(id, fx, fy);
				nodes.add(node);

				if (id.contains("EW")) {
					if (!mrtNodes.contains(node))
						mrtNodes.add(node);
					ewLine.add(node);
				}
				if (id.contains("NS")) {
					if (!mrtNodes.contains(node))
						mrtNodes.add(node);
					nsLine.add(node);
				}
				if (id.contains("NE")) {
					if (!mrtNodes.contains(node))
						mrtNodes.add(node);
					neLine.add(node);
				}
				if (id.contains("CC")) {
					if (!mrtNodes.contains(node))
						mrtNodes.add(node);
					ccLine.add(node);
				}
				if (id.contains("CG")) {
					if (!mrtNodes.contains(node))
						mrtNodes.add(node);
					cgLine.add(node);
				}
			} catch (NumberFormatException e) {
				System.out.println("warning: ignoring node " + id
						+ " (invalid position)");
			}
		} else {
			System.out.println("warning: ignoring node " + id
					+ " (already existing)");
		}
	}

	public void addStopAsNode(Stop stop) {
		if (nodeIndexMap.get(stop.getId()) == null)
			nodeIndexMap.put(stop.getId(), nodes.size());

		nodes.add(stop);
	}

	public void addEdge(Attributes attributes) {
		String id = attributes.getValue("id");
		String from = attributes.getValue("from");
		String to = attributes.getValue("to");

		if (edgeIndexMap.get(id) == null) {
			Integer f = getNodeIndex(from);
			Integer t = getNodeIndex(to);

			edgeIndexMap.put(id, edges.size());
			Edge edge = new Edge(this, id, f, t);
			edges.add(edge);

			String startEndEdgeId = from + "_" + to;

			if (startEndEdgeId.contains("NE3")
					&& !startEndEdgeId.contains("EW16"))
				startEndEdgeId = startEndEdgeId.replace("NE3", "EW16/NE3");

			if (from.equals("NS4") || to.equals("NS4"))
				startEndEdgeId = startEndEdgeId.replace("NS4", "BP1/NS4");

			if (from.equals("BP1") || to.equals("BP1"))
				startEndEdgeId = startEndEdgeId.replace("BP1", "BP1/NS4");

			startEndEdgeMap.put(startEndEdgeId, edge);
		} else {
			System.out.println(attributes.getValue("id"));
			System.out.println("warning: ignoring link " + id
					+ " (already existing)");
		}
	}

	public void addEdge(Edge edge) {
		String id = edge.getId();

		if (edgeIndexMap.get(id) == null) {
			edgeIndexMap.put(id, edges.size());
			edges.add(edge);
		}
	}

	public void addAgent(Long agentId, Agent agent) {
		if (!agentIdMap.containsKey(agentId))
			agentIdMap.put(agentId, agent);
	}

	public void addMRTTripSize() {
		this.mrtTrip++;
	}

	public int getMRTTripSize() {
		return this.mrtTrip;
	}

	public void cleanup() {
		// normalize nodes -> [0..1]
		for (Node node : nodes) {
			bound.add(node.getX(), node.getY(), 0);
		}

		for (Agent agent : agentIdMap.values()) {
			Itinerary itinerary = agent.getItinerary();
			for (Trip trip : itinerary.getTrips()) {
				for (Trip.TripSegment ts : trip.getTripSegments()) {
					bound.add(ts.getDepNode().getX(), ts.getDepNode().getY(),
							ts.getDepTime());
					bound.add(ts.getArrNode().getX(), ts.getArrNode().getY(),
							ts.getArrTime());
				}
			}
		}

		System.out.println("Sceen coordinates bound " + bound.toString());

		for (Node node : nodes) {
			node.setX(bound.normalizeX(node.getX()));
			node.setY(bound.normalizeY(node.getY()));
		}

		sortNodes(ewLine, "EW");
		sortNodes(nsLine, "NS");
		sortNodes(neLine, "NE");
		sortNodes(ccLine, "CC");
		sortNodes(cgLine, "CG");
	}

	private void sortNodes(List<Node> nodes, final String lineName) {
		Collections.sort(nodes, new Comparator<Node>() {

			@Override
			public int compare(Node o1, Node o2) {
				int n1Index = getIndex(o1, lineName);
				int n2Index = getIndex(o2, lineName);

				if (n1Index > n2Index)
					return 1;
				else if (n1Index < n2Index)
					return -1;
				else
					return 0;
			}

			private int getIndex(Node n, String line) {
				String id = n.getId();
				String[] names = id.split("/");
				String lineName = null;
				for (String name : names)
					if (name.contains(line)) {
						lineName = name;
						break;
					}
				return Integer.parseInt(lineName.replace(line, ""));
			}
		});
	}

	public SceneBound getBound() {
		if (bound == null)
			bound = new SceneBound();
		return bound;
	}

	public List<Node> getNodes() {
		return nodes;
	}

	public List<Edge> getEdges() {
		return edges;
	}

	public Map<Long, Agent> getAgentMap() {
		return agentIdMap;
	}

	public Integer getNodeIndex(String nodeName) {
		if (nodeName.equals("NE3"))
			return nodeIndexMap.get("EW16/NE3");
		if (nodeName.equals("BP1/NS4"))
			return nodeIndexMap.get("NS4");
		return nodeIndexMap.get(nodeName);
	}

	public Integer getEdgeIndex(String edge) {
		return edgeIndexMap.get(edge);
	}

	public Edge getEdge(String edge) {
		if (edgeIndexMap.get(edge) == null)
			return null;
		return edges.get(edgeIndexMap.get(edge));
	}

	public Edge getEdge(Node start, Node end) {
		String startId = start.getId(), endId = end.getId();
		if (startId.equals("NS4"))
			startId = "BP1/NS4";
		if (endId.equals("NS4"))
			endId = "BP1/NS4";

		Edge e = startEndEdgeMap.get(startId + "_" + endId);

		if (e == null) {
			e = new Edge(this, start.getId() + "_" + end.getId(),
					getNodeIndex(start.getId()), getNodeIndex(end.getId()));

			start.addOut(e);
			end.addIn(e);
			startEndEdgeMap.put(e.getId(), e);
		}

		return e;
	}

	public Node getNode(int index) {
		return this.nodes.get(index);
	}

	public Node getNode(String id) {
		Integer index = getNodeIndex(id);
		if (index == null)
			return null;
		return getNode(index);
	}

	public List<Node> getEWNodes() {
		return ewLine;
	}

	public List<Node> getNSNodes() {
		return nsLine;
	}

	public List<Node> getNENodes() {
		return neLine;
	}

	public List<Node> getCCNodes() {
		return ccLine;
	}

	public List<Node> getCGNodes() {
		return cgLine;
	}

	public List<Node> getMRTNodes() {
		return mrtNodes;
	}
}
